/* Copyright (c) 2020 XEPIC Corporation Limited */
/*
 * Copyright (c) 2000-2019 Stephen Williams (steve@icarus.com)
 *
 *    This source code is free software; you can redistribute it
 *    and/or modify it in source code form under the terms of the GNU
 *    General Public License as published by the Free Software
 *    Foundation; either version 2 of the License, or (at your option)
 *    any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
 * USA.
 */

#include "config.h"
#include "version_base.h"
#include "version_tag.h"

const char NOTICE[] = "";

const char HELP[] =
    "Usage: epicsim  [-g1995|-g2001|-g2005|-g2005-sv|-g2009|-g2012]\n"
    "                [-D macro[=defn]] [-I includedir] [-L moduledir]\n"
    "                [-m module] [-p flag=value]\n"
    "                [-s topmodule] [-t target] [-T min|typ|max]\n"
    "                [-W class] [-y dir] [-Y suf] [-l file] source_file(s)\n\n";

#define MAXSIZE 4096

#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif

#include <fcntl.h>

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif

#if !defined(WIFEXITED)
#define WIFEXITED(rc) ((rc & 0x7f) == 0)
#endif

#if !defined(WEXITSTATUS)
#define WEXITSTATUS(rc) (rc >> 8)
#endif

#include "cfparse_misc.h" /* cfparse() */
#include "globals.h"
#include "ivl_alloc.h"

const char sep = '/';
const char path_sep = ':';

extern void cfreset(FILE* fd, const char* path);

static char binBase[MAXSIZE];
static char libBase[MAXSIZE];

const char* vhdlpp_work = 0;
const char* mtm = 0;
const char* opath = "a.out";
const char* npath = 0;
const char* targ = "vvp";
const char* depfile = 0;

const char** vhdlpp_libdir = 0;
unsigned vhdlpp_libdir_cnt = 0;

char depmode = 'a';

const char* generation = "2005";
const char* gen_specify = "no-specify";
const char* gen_assertions = "assertions";
const char* gen_xtypes = "xtypes";
const char* gen_icarus = "icarus-misc";
const char* gen_io_range_error = "io-range-error";
const char* gen_strict_ca_eval = "no-strict-ca-eval";
const char* gen_strict_expr_width = "no-strict-expr-width";
const char* gen_shared_loop_index = "shared-loop-index";
const char* gen_verilog_ams = "no-verilog-ams";

/* Boolean: true means use a default include dir, false means don't */
int gen_std_include = 1;

/* Boolean: true means add the local file directory to the start
   of the include list. */
int gen_relative_include = 0;

char warning_flags[17] = "n";

int separate_compilation_flag = 0;

int gdb_frontend = 0;
static char compile_base[MAXSIZE];

/* Boolean: true means ignore errors about missing modules */
int ignore_missing_modules = 0;

unsigned integer_width = 32;

unsigned width_cap = 65536;

char* mod_list = 0;
char* command_filename = 0;

/* These are used to collect the list of file names that will be
   passed to ivlpp. Keep the list in a file because it can be a long
   list. */
char* source_path = 0;
FILE* source_file = 0;
unsigned source_count = 0;

char* defines_path = 0;
FILE* defines_file = 0;

char* iconfig_path = 0;
FILE* iconfig_file = 0;

char* compiled_defines_path = 0;

static char iconfig_common_path[4096] = "";

static const char** vpi_path_list = 0;
static unsigned vpi_path_list_size = 0;

static const char** env_vpi_path_list = 0;
static unsigned env_vpi_path_list_size = 0;

int synth_flag = 0;
int verbose_flag = 0;

FILE* fp;

char line[MAXSIZE];
char tmp[MAXSIZE];

/* Structure to keep a FIFO list of the command files */
typedef struct t_command_file {
  char* filename;
  struct t_command_file* next;
} s_command_file, *p_command_file;
p_command_file cmd_file_head = NULL; /* The FIFO head */
p_command_file cmd_file_tail = NULL; /* The FIFO tail */

/* Temporarily store parameter definition from command line and
 * parse it after we have dealt with command file
 */
static const char** defparm_base = 0;
static int defparm_size = 0;

/* Function to add a command file name to the FIFO. */
static void add_cmd_file(const char* filename) {
  p_command_file new;

  new = (p_command_file)malloc(sizeof(s_command_file));
  new->filename = strdup(filename);
  new->next = NULL;
  if (cmd_file_head == NULL) {
    cmd_file_head = new;
    cmd_file_tail = new;
  } else {
    cmd_file_tail->next = new;
    cmd_file_tail = new;
  }
}

/* Function to return the top command file name from the FIFO. */
static char* get_cmd_file(void) {
  char* filename;

  if (cmd_file_head == NULL)
    filename = NULL;
  else {
    p_command_file head;

    filename = cmd_file_head->filename;
    head = cmd_file_head;
    cmd_file_head = cmd_file_head->next;
    free(head);
  }
  return filename;
}

static FILE* fopen_safe(const char* path) {
  FILE* file = 0;
  int fd;

  fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0700);
  if (fd != -1) file = fdopen(fd, "w");

  return file;
}

static const char* my_tempfile(const char* str, FILE** fout) {
  FILE* file;
  int retry;

  static char pathbuf[8192];

  const char* tmpdir = getenv("TMP");
  if (tmpdir == 0) tmpdir = getenv("TMPDIR");
  if (tmpdir == 0) tmpdir = getenv("TEMP");
  if (tmpdir == 0) tmpdir = "/tmp";

  assert(tmpdir);
  assert((strlen(tmpdir) + strlen(str)) < sizeof pathbuf - 10);

  srand(getpid());
  retry = 100;
  file = NULL;
  while ((retry > 0) && (file == NULL)) {
    unsigned code = rand();
    snprintf(pathbuf, sizeof pathbuf, "%s%c%s%04x", tmpdir, sep, str, code);
    file = fopen_safe(pathbuf);
    retry -= 1;
  }

  *fout = file;
  return pathbuf;
}

static int t_version_only(void) {
  int rc;
  remove(source_path);
  free(source_path);

  fflush(0);
  snprintf(tmp, sizeof tmp, "%s%cepicsim-precompiler -V", libBase, sep);
  rc = system(tmp);
  if (rc != 0) {
    fprintf(stderr, "Unable to get version from \"%s\"\n", tmp);
  }

  fflush(0);
  snprintf(tmp, sizeof tmp, "%s%cepicsim-compiler -V -C\"%s\" -C\"%s\"",
           libBase, sep, iconfig_path, iconfig_common_path);
  rc = system(tmp);
  if (rc != 0) {
    fprintf(stderr, "Unable to get version from \"%s\"\n", tmp);
  }

  if (!getenv("EPICSIM_ICONFIG")) {
    remove(iconfig_path);
    free(iconfig_path);
    remove(defines_path);
    free(defines_path);
    remove(compiled_defines_path);
    free(compiled_defines_path);
  }

  return 0;
}

static inline void build_preprocess_command(int e_flag) {
  snprintf(tmp, sizeof tmp,
           "%s%cepicsim-precompiler %s%s%s -F\"%s\" -f\"%s\" -p\"%s\"%s",
           libBase, sep, verbose_flag ? " -v" : "", e_flag ? "" : " -L",
           strchr(warning_flags, 'r')
               ? " -Wredef-all "
               : strchr(warning_flags, 'R') ? " -Wredef-chg " : "",
           defines_path, source_path, compiled_defines_path,
           e_flag ? "" : " | ");
}

static int t_preprocess_only(void) {
  int rc;
  char* cmd;
  unsigned ncmd;

  build_preprocess_command(1);

  ncmd = strlen(tmp);
  cmd = malloc(ncmd + 1);
  strcpy(cmd, tmp);

  if (strcmp(opath, "-") != 0) {
    snprintf(tmp, sizeof tmp, " > \"%s\"", opath);
    cmd = realloc(cmd, ncmd + strlen(tmp) + 1);
    strcpy(cmd + ncmd, tmp);
    ncmd += strlen(tmp);
  }

  if (verbose_flag) printf("preprocess: %s\n", cmd);

  rc = system(cmd);
  remove(source_path);
  free(source_path);

  if (!getenv("EPICSIM_ICONFIG")) {
    remove(iconfig_path);
    free(iconfig_path);
    remove(defines_path);
    free(defines_path);
    remove(compiled_defines_path);
    free(compiled_defines_path);
  }

  if (rc != 0) {
    if (WIFEXITED(rc)) {
      fprintf(stderr, "errors preprocessing Verilog program.\n");
      free(cmd);
      return WEXITSTATUS(rc);
    }

    fprintf(stderr, "Command signaled: %s\n", cmd);
    free(cmd);
    return -1;
  }

  free(cmd);
  return 0;
}

/*
 * This is the default target type. It looks up the bits that are
 * needed to run the command from the configuration file (which is
 * already parsed for us) so we can handle must of the generic cases.
 */
static int t_compile(void) {
  unsigned rc;

  /* Start by building the preprocess command line, if required.
     This pipes into the main ivl command. */
  if (!separate_compilation_flag)
    build_preprocess_command(0);
  else
    strcpy(tmp, "");

  size_t ncmd = strlen(tmp);
  char* cmd = malloc(ncmd + 1);
  strcpy(cmd, tmp);

#ifndef __MINGW32__
  int rtn;
#endif

  /* Build the ivl command. */
  snprintf(tmp, sizeof tmp, "%s%s%cepicsim-compiler",
           gdb_frontend && separate_compilation_flag ? "gdb --args " : "",
           libBase, sep);

  rc = strlen(tmp);
  cmd = realloc(cmd, ncmd + rc + 1);
  strcpy(cmd + ncmd, tmp);
  ncmd += rc;

  if (verbose_flag) {
    const char* vv = " -v";
    rc = strlen(vv);
    cmd = realloc(cmd, ncmd + rc + 1);
    strcpy(cmd + ncmd, vv);
    ncmd += rc;
  }

  if (npath != 0) {
    snprintf(tmp, sizeof tmp, " -N\"%s\"", npath);
    rc = strlen(tmp);
    cmd = realloc(cmd, ncmd + rc + 1);
    strcpy(cmd + ncmd, tmp);
    ncmd += rc;
  }

  snprintf(tmp, sizeof tmp, " -C\"%s\"", iconfig_path);
  rc = strlen(tmp);
  cmd = realloc(cmd, ncmd + rc + 1);
  strcpy(cmd + ncmd, tmp);
  ncmd += rc;

  snprintf(tmp, sizeof tmp, " -C\"%s\"", iconfig_common_path);
  rc = strlen(tmp);
  cmd = realloc(cmd, ncmd + rc + 1);
  strcpy(cmd + ncmd, tmp);
  ncmd += rc;

  if (separate_compilation_flag)
    snprintf(tmp, sizeof tmp, " -F\"%s\"", source_path);
  else
    snprintf(tmp, sizeof tmp, " -- -");
  rc = strlen(tmp);
  cmd = realloc(cmd, ncmd + rc + 1);
  strcpy(cmd + ncmd, tmp);
  ncmd += rc;

  if (verbose_flag) printf("translate: %s\n", cmd);

  rc = system(cmd);
  if (!getenv("EPICSIM_ICONFIG")) {
    remove(source_path);
    free(source_path);
    remove(iconfig_path);
    free(iconfig_path);
    remove(defines_path);
    free(defines_path);
    remove(compiled_defines_path);
    free(compiled_defines_path);
  }
  rtn = 0;
  if (rc != 0) {
    if (rc == 127) {
      fprintf(stderr, "Failed to execute: %s\n", cmd);
      rtn = 1;
    } else if (WIFEXITED(rc)) {
      rtn = WEXITSTATUS(rc);
    } else {
      fprintf(stderr, "Command signaled: %s\n", cmd);
      rtn = -1;
    }
  }

  free(cmd);
  return rtn;
}

static void process_warning_switch(const char* name) {
  if (strcmp(name, "all") == 0) {
    process_warning_switch("anachronisms");
    process_warning_switch("implicit");
    process_warning_switch("implicit-dimensions");
    process_warning_switch("macro-replacement");
    process_warning_switch("portbind");
    process_warning_switch("select-range");
    process_warning_switch("timescale");
    process_warning_switch("sensitivity-entire-array");
  } else if (strcmp(name, "anachronisms") == 0) {
    if (!strchr(warning_flags, 'n')) strcat(warning_flags, "n");
  } else if (strcmp(name, "floating-nets") == 0) {
    if (!strchr(warning_flags, 'f')) strcat(warning_flags, "f");
  } else if (strcmp(name, "implicit") == 0) {
    if (!strchr(warning_flags, 'i')) strcat(warning_flags, "i");
  } else if (strcmp(name, "implicit-dimensions") == 0) {
    if (!strchr(warning_flags, 'd')) strcat(warning_flags, "d");
  } else if (strcmp(name, "macro-redefinition") == 0) {
    if (!strchr(warning_flags, 'r')) strcat(warning_flags, "r");
  } else if (strcmp(name, "macro-replacement") == 0) {
    if (!strchr(warning_flags, 'R')) strcat(warning_flags, "R");
  } else if (strcmp(name, "portbind") == 0) {
    if (!strchr(warning_flags, 'p')) strcat(warning_flags, "p");
  } else if (strcmp(name, "select-range") == 0) {
    if (!strchr(warning_flags, 's')) strcat(warning_flags, "s");
  } else if (strcmp(name, "timescale") == 0) {
    if (!strchr(warning_flags, 't')) strcat(warning_flags, "t");
    /* Since the infinite loop check is not part of 'all' it
     * does not have a no- version. */
  } else if (strcmp(name, "infloop") == 0) {
    if (!strchr(warning_flags, 'l')) strcat(warning_flags, "l");
  } else if (strcmp(name, "sensitivity-entire-vector") == 0) {
    if (!strchr(warning_flags, 'v')) strcat(warning_flags, "v");
  } else if (strcmp(name, "sensitivity-entire-array") == 0) {
    if (!strchr(warning_flags, 'a')) strcat(warning_flags, "a");
  } else if (strcmp(name, "no-anachronisms") == 0) {
    char* cp = strchr(warning_flags, 'n');
    if (cp)
      while (*cp) {
        cp[0] = cp[1];
        cp += 1;
      }
  } else if (strcmp(name, "no-floating-nets") == 0) {
    char* cp = strchr(warning_flags, 'f');
    if (cp)
      while (*cp) {
        cp[0] = cp[1];
        cp += 1;
      }
  } else if (strcmp(name, "no-implicit") == 0) {
    char* cp = strchr(warning_flags, 'i');
    if (cp)
      while (*cp) {
        cp[0] = cp[1];
        cp += 1;
      }
  } else if (strcmp(name, "no-implicit-dimensions") == 0) {
    char* cp = strchr(warning_flags, 'd');
    if (cp)
      while (*cp) {
        cp[0] = cp[1];
        cp += 1;
      }
  } else if (strcmp(name, "no-macro-redefinition") == 0) {
    char* cp = strchr(warning_flags, 'r');
    if (cp)
      while (*cp) {
        cp[0] = cp[1];
        cp += 1;
      }
    cp = strchr(warning_flags, 'R');
    if (cp)
      while (*cp) {
        cp[0] = cp[1];
        cp += 1;
      }
  } else if (strcmp(name, "no-portbind") == 0) {
    char* cp = strchr(warning_flags, 'p');
    if (cp)
      while (*cp) {
        cp[0] = cp[1];
        cp += 1;
      }
  } else if (strcmp(name, "no-select-range") == 0) {
    char* cp = strchr(warning_flags, 's');
    if (cp)
      while (*cp) {
        cp[0] = cp[1];
        cp += 1;
      }
  } else if (strcmp(name, "no-timescale") == 0) {
    char* cp = strchr(warning_flags, 't');
    if (cp)
      while (*cp) {
        cp[0] = cp[1];
        cp += 1;
      }
  } else if (strcmp(name, "no-sensitivity-entire-vector") == 0) {
    char* cp = strchr(warning_flags, 'v');
    if (cp)
      while (*cp) {
        cp[0] = cp[1];
        cp += 1;
      }
  } else if (strcmp(name, "no-sensitivity-entire-array") == 0) {
    char* cp = strchr(warning_flags, 'a');
    if (cp)
      while (*cp) {
        cp[0] = cp[1];
        cp += 1;
      }
  } else {
    fprintf(stderr,
            "Ignoring unknown warning class "
            "%s\n",
            name);
  }
}

void process_library_switch(const char* name) {
  fprintf(iconfig_file, "-y:%s\n", name);
}

void process_library_nocase_switch(const char* name) {
  fprintf(iconfig_file, "-yl:%s\n", name);
}

void process_library2_switch(const char* name) {
  fprintf(iconfig_file, "-Y:%s\n", name);
}

void process_include_dir(const char* name) {
  fprintf(defines_file, "I:%s\n", name);
}

void process_define(const char* name) { fprintf(defines_file, "D:%s\n", name); }

void process_parameter(const char* name) {
  fprintf(iconfig_file, "defparam:%s\n", name);
}

void process_timescale(const char* ts_string) {
  fprintf(iconfig_file, "timescale:%s\n", ts_string);
}

/*
 * This function is called while processing a file name in a command
 * file, or a file name on the command line. Look to see if there is a
 * .sft or .vpi suffix, and if so pass that as a sys_func or module
 * file. Otherwise, it is a Verilog source file to be written into the
 * file list.
 */
void process_file_name(const char* name, int lib_flag) {
  if (strlen(name) > 4 && strcasecmp(".sft", name + strlen(name) - 4) == 0) {
    fprintf(stderr,
            "SFT files are deprecated. Please pass the VPI module instead.\n");
    fprintf(iconfig_file, "sys_func:%s\n", name);

  } else if (strlen(name) > 4 &&
             strcasecmp(".vpi", name + strlen(name) - 4) == 0) {
    fprintf(iconfig_file, "module:%s\n", name);

  } else {
    fprintf(source_file, "%s\n", name);
    source_count += 1;
    if (lib_flag) fprintf(iconfig_file, "library_file:%s\n", name);
  }
}

static int process_generation(const char* name) {
  if (strcmp(name, "1995") == 0)
    generation = "1995";

  else if (strcmp(name, "2001") == 0)
    generation = "2001";

  else if (strcmp(name, "2001-noconfig") == 0)
    generation = "2001-noconfig";

  else if (strcmp(name, "2005") == 0)
    generation = "2005";

  else if (strcmp(name, "2005-sv") == 0)
    generation = "2005-sv";

  else if (strcmp(name, "2009") == 0)
    generation = "2009";

  else if (strcmp(name, "2012") == 0)
    generation = "2012";

  else if (strcmp(name, "1") == 0) { /* Deprecated: use 1995 */
    generation = "1995";
    gen_xtypes = "no-xtypes";
    gen_icarus = "no-icarus-misc";

  } else if (strcmp(name, "2") == 0) { /* Deprecated: use 2001 */
    generation = "2001";
    gen_xtypes = "no-xtypes";
    gen_icarus = "no-icarus-misc";

  } else if (strcmp(name, "2x") == 0) { /* Deprecated: use 2005/xtypes */
    generation = "2005";
    gen_xtypes = "xtypes";
    gen_icarus = "icarus-misc";

  } else if (strcmp(name, "xtypes") == 0)
    gen_xtypes = "xtypes";

  else if (strcmp(name, "no-xtypes") == 0)
    gen_xtypes = "no-xtypes";

  else if (strcmp(name, "icarus-misc") == 0)
    gen_icarus = "icarus-misc";

  else if (strcmp(name, "no-icarus-misc") == 0)
    gen_icarus = "no-icarus-misc";

  else if (strcmp(name, "specify") == 0)
    gen_specify = "specify";

  else if (strcmp(name, "no-specify") == 0)
    gen_specify = "no-specify";

  else if (strcmp(name, "assertions") == 0)
    gen_assertions = "assertions";

  else if (strcmp(name, "supported-assertions") == 0)
    gen_assertions = "supported-assertions";

  else if (strcmp(name, "no-assertions") == 0)
    gen_assertions = "no-assertions";

  else if (strcmp(name, "std-include") == 0)
    gen_std_include = 1;

  else if (strcmp(name, "no-std-include") == 0)
    gen_std_include = 0;

  else if (strcmp(name, "relative-include") == 0)
    gen_relative_include = 1;

  else if (strcmp(name, "no-relative-include") == 0)
    gen_relative_include = 0;

  else if (strcmp(name, "io-range-error") == 0)
    gen_io_range_error = "io-range-error";

  else if (strcmp(name, "no-io-range-error") == 0)
    gen_io_range_error = "no-io-range-error";

  else if (strcmp(name, "strict-ca-eval") == 0)
    gen_strict_ca_eval = "strict-ca-eval";

  else if (strcmp(name, "no-strict-ca-eval") == 0)
    gen_strict_ca_eval = "no-strict-ca-eval";

  else if (strcmp(name, "strict-expr-width") == 0)
    gen_strict_expr_width = "strict-expr-width";

  else if (strcmp(name, "no-strict-expr-width") == 0)
    gen_strict_expr_width = "no-strict-expr-width";

  else if (strcmp(name, "shared-loop-index") == 0)
    gen_shared_loop_index = "shared-loop-index";

  else if (strcmp(name, "no-shared-loop-index") == 0)
    gen_shared_loop_index = "no-shared-loop-index";

  else if (strcmp(name, "verilog-ams") == 0)
    gen_verilog_ams = "verilog-ams";

  else if (strcmp(name, "no-verilog-ams") == 0)
    gen_verilog_ams = "no-verilog-ams";

  else {
    fprintf(stderr,
            "Unknown/Unsupported Language generation "
            "%s\n\n",
            name);
    fprintf(stderr, "Supported generations are:\n");
    fprintf(stderr,
            "    1995    -- IEEE1364-1995\n"
            "    2001    -- IEEE1364-2001\n"
            "    2005    -- IEEE1364-2005\n"
            "    2005-sv -- IEEE1800-2005\n"
            "    2009    -- IEEE1800-2009\n"
            "    2012    -- IEEE1800-2012\n"
            "Other generation flags:\n"
            "    assertions | supported-assertions | no-assertions\n"
            "    specify | no-specify\n"
            "    verilog-ams | no-verilog-ams\n"
            "    std-include | no-std-include\n"
            "    relative-include | no-relative-include\n"
            "    xtypes | no-xtypes\n"
            "    icarus-misc | no-icarus-misc\n"
            "    io-range-error | no-io-range-error\n"
            "    strict-ca-eval | no-strict-ca-eval\n"
            "    strict-expr-width | no-strict-expr-width\n"
            "    shared-loop-index | no-shared-loop-index\n");

    return 1;
  }

  return 0;
}

static int process_depfile(const char* name) {
  const char* cp = strchr(name, '=');
  if (cp) {
    int match_length = (int)(cp - name) + 1;
    if (strncmp(name, "all=", match_length) == 0) {
      depmode = 'a';
    } else if (strncmp(name, "include=", match_length) == 0) {
      depmode = 'i';
    } else if (strncmp(name, "module=", match_length) == 0) {
      depmode = 'm';
    } else if (strncmp(name, "prefix=", match_length) == 0) {
      depmode = 'p';
    } else {
      fprintf(stderr, "Unknown dependency file mode '%.*s'\n\n",
              match_length - 1, name);
      fprintf(stderr, "Supported modes are:\n");
      fprintf(stderr, "    all\n");
      fprintf(stderr, "    include\n");
      fprintf(stderr, "    module\n");
      fprintf(stderr, "    prefix\n");
      return -1;
    }
    depfile = cp + 1;
  } else {
    depmode = 'a';
    depfile = name;
  }
  return 0;
}

static void add_env_vpi_module_path(const char* path) {
  env_vpi_path_list_size += 1;
  env_vpi_path_list = (const char**)realloc(
      env_vpi_path_list, env_vpi_path_list_size * sizeof(char*));
  env_vpi_path_list[env_vpi_path_list_size - 1] = path;
}

static void get_env_vpi_module_paths(void) {
  char* var = getenv("EPICSIM_VPI_MODULE_PATH");
  char *ptr, *end;

  if (!var) return;

  var = strdup(var);
  ptr = var;
  end = var + strlen(var);
  int len = 0;
  while (ptr <= end) {
    if (*ptr == 0 || *ptr == path_sep) {
      *ptr = 0;
      if (len > 0) {
        add_env_vpi_module_path(var);
      }
      len = 0;
      var = ptr + 1;
    } else {
      len++;
    }
    ptr++;
  }
}

static void add_vpi_module_path(const char* path) {
  vpi_path_list_size += 1;
  vpi_path_list =
      (const char**)realloc(vpi_path_list, vpi_path_list_size * sizeof(char*));
  vpi_path_list[vpi_path_list_size - 1] = path;
}

static int probe_for_vpi_module(const char* base_path, const char* name,
                                char* path, unsigned path_size) {
  snprintf(path, path_size, "%s%c%s.vpi", base_path, sep, name);
  if (access(path, R_OK) == 0) return 1;

  snprintf(path, path_size, "%s%c%s.vpl", base_path, sep, name);
  if (access(path, R_OK) == 0) return 1;

  return 0;
}

/*
 * If it exists add the VPI file for the given module.
 */
static void add_vpi_file(const char* name) {
  const char* base_dir = libBase;

  char path[4096];

  int found = 0;
  if (strchr(name, sep)) {
    /* If the name has at least one directory character in it
       then assume it is a complete name, maybe including any
       possible .vpi or .vpl suffix. */
    found = access(name, R_OK) == 0;
    if (!found) {
      snprintf(path, sizeof(path), "%s.vpi", name);
      found = access(path, R_OK) == 0;
      if (!found) {
        snprintf(path, sizeof(path), "%s.vpl", name);
        found = access(path, R_OK) == 0;
      }
    } else {
      strncpy(path, name, sizeof(path) - 1);
    }
  } else {
    for (unsigned idx = 0; !found && (idx < vpi_path_list_size); idx += 1) {
      found =
          probe_for_vpi_module(vpi_path_list[idx], name, path, sizeof(path));
    }
    for (unsigned idx = 0; !found && (idx < env_vpi_path_list_size); idx += 1) {
      found = probe_for_vpi_module(env_vpi_path_list[idx], name, path,
                                   sizeof(path));
    }
    if (!found) {
      found = probe_for_vpi_module(base_dir, name, path, sizeof(path));
    }
  }
  if (found) {
    fprintf(iconfig_file, "module:%s\n", path);
  } else {
    fprintf(stderr, "Unable to find VPI module '%s'\n", name);
  }
}

static int FindBasePath(void) {
  char basePath[MAXSIZE];
  char* s;
  int len;
  len = readlink("/proc/self/exe", basePath, sizeof basePath);
  if (len >= (ssize_t)sizeof basePath) {
    return -1;
  }
  if (len <= 0) {
    return -1;
  }
  s = strrchr(basePath, sep);
  if (s == 0) {
    return -1;
  }
  *s = 0;
  s = strrchr(basePath, sep);
  if (s == 0) {
    return -1;
  }
  *s = 0;
  len = s - basePath;
  snprintf(binBase, MAXSIZE, "%s%cbin", basePath, sep);
  snprintf(libBase, MAXSIZE, "%s%clib%cepicsim", basePath, sep, sep);
  return 0;
}

int main(int argc, char** argv) {
  int e_flag = 0;
  int version_flag = 0;
  int opt;

  if (FindBasePath()) {
    return -1;
  }

  get_env_vpi_module_paths();

  /* Create a temporary file for communicating input parameters
     to the preprocessor. */
  source_path = strdup(my_tempfile("ivrlg", &source_file));
  if (NULL == source_file) {
    fprintf(stderr, "%s: Error opening temporary file %s\n", argv[0],
            source_path);
    fprintf(stderr, "%s: Please check TMP or TMPDIR.\n", argv[0]);
    return 1;
  }

  defines_path = strdup(my_tempfile("ivrlg2", &defines_file));
  if (NULL == defines_file) {
    fprintf(stderr, "%s: Error opening temporary file %s\n", argv[0],
            defines_path);
    fprintf(stderr, "%s: Please check TMP or TMPDIR.\n", argv[0]);

    fclose(source_file);
    remove(source_path);
    return 1;
  }

  fprintf(defines_file, "D:__ICARUS__=1\n");
  if (strcmp(gen_verilog_ams, "verilog-ams") == 0)
    fprintf(defines_file, "D:__VAMS_ENABLE__=1\n");

  /* Create another temporary file for passing configuration
     information to ivl. */

  if ((iconfig_path = getenv("EPICSIM_ICONFIG"))) {
    fprintf(stderr, "%s: EPICSIM_ICONFIG=%s\n", argv[0], iconfig_path);

    iconfig_file = fopen(iconfig_path, "w");

  } else {
    iconfig_path = strdup(my_tempfile("ivrlh", &iconfig_file));
  }

  if (NULL == iconfig_file) {
    fprintf(stderr, "%s: Error opening temporary file %s\n", argv[0],
            iconfig_path);
    fprintf(stderr, "%s: Please check TMP or TMPDIR.\n", argv[0]);
    fclose(source_file);
    remove(source_path);

    fclose(defines_file);
    remove(defines_path);
    return 1;
  }

  /* Create a temporary file (I only really need the path) that
     can carry preprocessor precompiled defines to the library. */
  {
    FILE* tmp_file = 0;
    compiled_defines_path = strdup(my_tempfile("ivrli", &tmp_file));
    if (tmp_file) {
      fclose(tmp_file);
    }
  }

  while ((opt = getopt(argc, argv,
                       "c:D:d:Ef:g:hl:I:iL:M:m:N:o:P:p:Ss:T:t:uvVW:y:Y:")) !=
         EOF) {
    switch (opt) {
      case 'c':
      case 'f':
        add_cmd_file(optarg);
        break;
      case 'D':
        process_define(optarg);
        break;
      case 'E':
        e_flag = 1;
        break;
      case 'P':
        defparm_size += 1;
        defparm_base =
            (const char**)realloc(defparm_base, defparm_size * sizeof(char*));
        defparm_base[defparm_size - 1] = optarg;
        break;
      case 'p':
        fprintf(iconfig_file, "flag:%s\n", optarg);
        break;
      case 'd':
        fprintf(iconfig_file, "debug:%s\n", optarg);
        break;

      case 'g':
        if (strcmp(optarg, "db") == 0)
          gdb_frontend = 1;
        else if (process_generation(optarg) != 0)
          return -1;
        break;
      case 'h':
        fprintf(stderr, "%s\n", HELP);
        return 1;

      case 'I':
        process_include_dir(optarg);
        break;

      case 'i':
        ignore_missing_modules = 1;
        break;

      case 'L':
        add_vpi_module_path(optarg);
        break;

      case 'l':
        process_file_name(optarg, 1);
        break;

      case 'M':
        if (process_depfile(optarg) != 0) return -1;
        break;

      case 'm':
        add_vpi_file(optarg);
        break;

      case 'N':
        npath = optarg;
        break;

      case 'o':
        opath = optarg;
        break;

      case 'S':
        synth_flag = 1;
        break;
      case 's':
        fprintf(iconfig_file, "root:%s\n", optarg);
        break;
      case 'T':
        if (strcmp(optarg, "min") == 0) {
          mtm = "min";
        } else if (strcmp(optarg, "typ") == 0) {
          mtm = "typ";
        } else if (strcmp(optarg, "max") == 0) {
          mtm = "max";
        } else {
          fprintf(stderr, "%s: invalid -T%s argument\n", argv[0], optarg);
          return 1;
        }
        break;
      case 't':
        targ = optarg;
        break;
      case 'u':
        separate_compilation_flag = 1;
        break;
      case 'v':
        verbose_flag = 1;
        break;
      case 'V':
        version_flag = 1;
        break;
      case 'W':
        process_warning_switch(optarg);
        break;
      case 'y':
        process_library_switch(optarg);
        break;
      case 'Y':
        process_library2_switch(optarg);
        break;
      case '?':
      default:
        fclose(source_file);
        remove(source_path);
        free(source_path);
        fclose(defines_file);
        remove(defines_path);
        free(defines_path);
        fclose(iconfig_file);
        remove(iconfig_path);
        free(iconfig_path);
        remove(compiled_defines_path);
        free(compiled_defines_path);
        while ((command_filename = get_cmd_file())) {
          free(command_filename);
        }
        return 1;
    }
  }

  if (version_flag || verbose_flag) {
    printf("EpicSim version " VERSION " (" VERSION_TAG ")\n\n");
    printf("Copyright (c) 2020 by XEPIC Co., Ltd.\n\n");
    puts(NOTICE);
  }

  /* Make a common conf file path to reflect the target. */
  snprintf(iconfig_common_path, sizeof iconfig_common_path, "%s%c%s%s.conf",
           libBase, sep, targ, synth_flag ? "-s" : "");

  /* Write values to the iconfig file. */
  fprintf(iconfig_file, "basedir:%s\n", libBase);

  /* Tell the core where to find the system VPI modules. */
  fprintf(iconfig_file, "module:%s%csystem.vpi\n", libBase, sep);
  fprintf(iconfig_file, "module:%s%cvhdl_sys.vpi\n", libBase, sep);
  fprintf(iconfig_file, "module:%s%cvhdl_textio.vpi\n", libBase, sep);

  /* If verilog-2005/09/12 is enabled or icarus-misc or verilog-ams,
   * then include the v2005_math library. */
  if (strcmp(generation, "2005") == 0 || strcmp(generation, "2009") == 0 ||
      strcmp(generation, "2012") == 0 ||
      strcmp(gen_icarus, "icarus-misc") == 0 ||
      strcmp(gen_verilog_ams, "verilog-ams") == 0) {
    fprintf(iconfig_file, "module:%s%cv2005_math.vpi\n", libBase, sep);
  }
  /* If verilog-ams or icarus_misc is enabled, then include the
   * va_math module as well. */
  if (strcmp(gen_verilog_ams, "verilog-ams") == 0 ||
      strcmp(gen_icarus, "icarus-misc") == 0) {
    fprintf(iconfig_file, "module:%s%cva_math.vpi\n", libBase, sep);
  }
  /* If verilog-2009 (SystemVerilog) is enabled, then include the
     v2009 module. */
  if (strcmp(generation, "2005-sv") == 0 || strcmp(generation, "2009") == 0 ||
      strcmp(generation, "2012") == 0) {
    fprintf(iconfig_file, "module:%s%cv2009.vpi\n", libBase, sep);
  }

  if (mtm != 0) fprintf(iconfig_file, "-T:%s\n", mtm);
  fprintf(iconfig_file, "generation:%s\n", generation);
  fprintf(iconfig_file, "generation:%s\n", gen_specify);
  fprintf(iconfig_file, "generation:%s\n", gen_assertions);
  fprintf(iconfig_file, "generation:%s\n", gen_xtypes);
  fprintf(iconfig_file, "generation:%s\n", gen_io_range_error);
  fprintf(iconfig_file, "generation:%s\n", gen_strict_ca_eval);
  fprintf(iconfig_file, "generation:%s\n", gen_strict_expr_width);
  fprintf(iconfig_file, "generation:%s\n", gen_shared_loop_index);
  fprintf(iconfig_file, "generation:%s\n", gen_verilog_ams);
  fprintf(iconfig_file, "generation:%s\n", gen_icarus);
  fprintf(iconfig_file, "warnings:%s\n", warning_flags);
  fprintf(iconfig_file, "ignore_missing_modules:%s\n",
          ignore_missing_modules ? "true" : "false");
  fprintf(iconfig_file, "out:%s\n", opath);
  if (depfile) {
    fprintf(iconfig_file, "depfile:%s\n", depfile);
    fprintf(iconfig_file, "depmode:%c\n", depmode);
  }

  while ((command_filename = get_cmd_file())) {
    int rc;

    if ((fp = fopen(command_filename, "r")) == NULL) {
      fprintf(stderr,
              "%s: cannot open command file %s "
              "for reading.\n",
              argv[0], command_filename);
      return 1;
    }

    cfreset(fp, command_filename);
    rc = cfparse();
    if (rc != 0) {
      fprintf(stderr,
              "%s: parsing failed in base command "
              "file %s.\n",
              argv[0], command_filename);
      return 1;
    }
    free(command_filename);
    fclose(fp);
  }
  destroy_lexor();

  if (depfile) {
    fprintf(defines_file, "M%c:%s\n", depmode, depfile);
  }

  if (vhdlpp_work == 0) vhdlpp_work = "ivl_vhdl_work";
  fprintf(defines_file, "vhdlpp:%s%cvhdlpp\n", libBase, sep);
  fprintf(defines_file, "vhdlpp-work:%s\n", vhdlpp_work);
  for (unsigned idx = 0; idx < vhdlpp_libdir_cnt; idx += 1)
    fprintf(defines_file, "vhdlpp-libdir:%s\n", vhdlpp_libdir[idx]);

  /* Process parameter definition from command line. The last
     defined would override previous ones. */
  int pitr;
  for (pitr = 0; pitr < defparm_size; pitr++)
    process_parameter(defparm_base[pitr]);
  free(defparm_base);
  defparm_base = 0;
  defparm_size = 0;

  /* Finally, process all the remaining words on the command
     line as file names. */
  for (int idx = optind; idx < argc; idx += 1) process_file_name(argv[idx], 0);

  /* If the use of a default include directory is not
     specifically disabled, then write that directory as the
     very last include directory to use... always. */
  if (gen_std_include) {
    fprintf(defines_file, "I:%s%cinclude\n", libBase, sep);
  }

  if (gen_relative_include) {
    fprintf(defines_file, "relative include:true\n");
  } else {
    fprintf(defines_file, "relative include:false\n");
  }

  fclose(source_file);
  source_file = 0;

  fclose(defines_file);
  defines_file = 0;

  /* If we are planning on opening a dependencies file, then
     open and truncate it here. The other phases of compilation
     will append to the file, so this is necessary to make sure
     it starts out empty. */
  if (depfile) {
    FILE* fd = fopen(depfile, "w");
    if (!fd) {
      fprintf(stderr, "%s: can't open %s file.\n\n%s\n", argv[0], depfile,
              HELP);
      return 1;
    }
    fclose(fd);
  }

  if (source_count == 0 && !version_flag) {
    fprintf(stderr, "%s: no source files.\n\n%s\n", argv[0], HELP);
    return 1;
  }

  fprintf(iconfig_file, "iwidth:%u\n", integer_width);

  fprintf(iconfig_file, "widthcap:%u\n", width_cap);

  /* Write the preprocessor command needed to preprocess a
     single file. This may be used to preprocess library
     files. */
  fprintf(iconfig_file,
          "ivlpp:%s%cepicsim-precompiler %s -L -F\"%s\" -P\"%s\"\n", libBase,
          sep,
          strchr(warning_flags, 'r')
              ? "-Wredef-all"
              : strchr(warning_flags, 'R') ? "-Wredef-chg" : "",
          defines_path, compiled_defines_path);

  /* Done writing to the iconfig file. Close it now. */
  fclose(iconfig_file);

  /* If we're only here for the version output, then we're done. */
  if (version_flag) return t_version_only();

  /* If the -E flag was given on the command line, then all we
     do is run the preprocessor and put the output where the
     user wants it. */
  if (e_flag) return t_preprocess_only();

  /* Otherwise, this is a full compile. */
  return t_compile();
}
